iT邦幫忙

2023 iThome 鐵人賽

DAY 26
0

今天我們會進一步介紹如何做元件層級的依賴注入。

程式碼請參考 D26/effect-context

https://ithelp.ithome.com.tw/upload/images/20231011/20158615vrshPxpa7e.png

我們以 UsersField 這個元件為例,他有幾個地方會觸發事件

  • Badges 按叉叉
  • 輸入欄位的值改變
  • 使用者在輸入欄位按下鍵盤
  • 使用者按下 Add 按鈕

不論是哪一種,都會和元件當前 狀態 互動,而且通常都會造成 副作用,例如 addUser 背後其實會呼叫 getUser,等到執行完畢還會做 setUsersField。我們雖然討厭副作用,但完全把它變不見式不可能的,所以我們只能把它 延遲處理。藉由把它們當作參數傳入負責資料處理的函式中,就能確保這些函式至少在被呼叫前是純淨的,而純淨函式則代表著容易閱讀、容易維護與容易測試。

https://ithelp.ithome.com.tw/upload/images/20231011/20158615EqvDNYy6oJ.png

  • 把 contract 放到全域的服務中,由 atom 引用進來

    這邊可參考 D12 - 設計依賴注入D13 - 實作依賴注入

    xport interface Constants {
     settings: Settings
     texts: Texts
    
    
    xport interface Functions {
     clock: Clock
     contracts: Contracts // < Functions 新增了 Contracts
    
    
    xport interface Service extends Constants, Functions {}
    ``
    
    
  • 利用一個型別,含括元件內部狀態,例如上圖中藍色與綠色圈圈
    / 屬於 UsersField
    xport interface UsersFieldService {
     getUser: GetUser
     field: UsersField
     setField: SetAtom<UsersField>
    
    ``
    
    

把這些共通狀態抽出來以後,我們就可以利用 Effect context 來做一個引子

export const UsersFieldService = {
  context: Context.Tag<UsersFieldService>(),
}

還記得 Effect<R, E, A> 三本柱嗎 ? Effect context 就用來提供裡面的 R
(the parameter R represents the contextual data required by the effect to be executed)

const onAddUser = (
  _: AddUserEvent
): Effect.Effect<UsersFieldService, never, UsersField> => {
  return pipe(
    UsersFieldService.context, 
    // 取得 UsersFieldService,從裡面解構出 getUser 函式和 field
    // 最後產生執行 getUser 產生另一個 effect
    Effect.flatMap(({ getUser, field }) => getUser(field.input)),
    // 由於 updateError 和 addUser 都會產生 effect
    // 所以使用 matchEffect 來把它們合併後再攤平
    Effect.matchEffect({
      onFailure: updateError,
      onSuccess: addUser,
    })
  )
}

最終看到整個函式的輸出型別為 Effect.Effect<UsersFieldService, never, UsersField> ,表示他可以藉由 effect 型別盒子來和其他 effect 做 flatMap 或是 map 等等運算。

此外由於排除了 getUser 這個副作用函式,在我們進行測試時就不用再啟用 mockserver,只需要變更傳入的 UsersFieldService 即可。

//src\app\edit\courses\all\data\states\add-course-form\users-field\index.spec.ts

 it('should return invalid user field with error when get user failed', async () => {
    //arrange
    const event = AddUserEvent.self
    const fakeGetUserError = UnexpectedRequestError.of({
      error: '',
      method: 'GET',
      url: '',
    })
    const service = UsersFieldService.of({
      getUser: () => Effect.fail(fakeGetUserError),
      field: InvalidUsersField.ofInput('richard_01'),
      setField: () => {},
    })
    //act
    const result = await pipe(
      UsersField.on(event),
      Effect.provideService(UsersFieldService.context, service), 
      Effect.runPromise
    )
    //assert
    expect(result._tag).toBe('InvalidUsersField')
    expect(result.error._tag).toBe('Some')
  })

以上程式另一個重點是 Effect.provideService(context, instance) 只要使用它就可以對含有 REffect 完成依賴注入。


有時候我們會遇到某一個依賴需要另外一個依賴的情形,明天會介紹這個時候 effect 可以怎麼幫助我們管理這些複雜的依賴關係。


上一篇
D25 - 資料傳輸模型
下一篇
D27 - 管理依賴注入 (一)
系列文
從 Next.js 開始的 Functional Programming30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言